<template>
  <div :class="boxCls" :style="boxStyle" ref="box">
    <AxisMark />
    <BaseCube
      v-for="item in cubeList_res"
      :key="item.key"
      v-bind="item"
      @startTurn="(position) => onStartTurn(item, position)"
      @endTurn="(position) => onEndTurn(item, position)"
      @afterUpdated="afterUpdated(item)"
    ></BaseCube>
  </div>
</template>
<script lang="tsx">
import _groupBy from "lodash/groupBy";
import _cloneDeep from "lodash/cloneDeep";
import {
  Vue,
  Component,
  Prop,
  ProvideReactive,
  InjectReactive,
  Watch,
  Ref,
} from "vue-property-decorator";
import {
  CubeProps,
  Size,
  getCoordMaxNum,
  getCubeList,
  CubeCoord,
  FacePositionBase,
  Axis,
  Direction,
  getAxisChangeOrderInfo,
  positiveFace,
  getDeductionCoordList,
  positionQuiteAxisObject,
} from "@/common/utils";
import BaseCube from "./baseCube.vue";
import RotateStyleCtrl from "./rotateStyleCtrl.vue";
import AxisMark from "./axisMark.vue";

type CubePropsGroup = CubeProps[][];
interface CubePropsGroupMap {
  [key: number]: CubeProps[];
}

// 旋转配置
interface TurnInfo {
  position: FacePositionBase;
  config: CubeProps;
}

// 旋转后信息切换映射
interface RotateChangeMap {
  [key: string]: CubeProps;
}

const prefix = "rubik-cube";
@Component({
  name: "RubikCube",
  components: {
    BaseCube,
    AxisMark,
  },
})
export default class extends Vue {
  static RotateStyleCtrl = RotateStyleCtrl;

  // props
  // 几阶魔方
  @Prop({
    type: Number,
    default: 3,
  })
  readonly order!: number;

  @Prop({
    type: Number,
    default: 150,
  })
  readonly cubeSize!: Size;

  @Prop({
    type: Number,
    default: 1,
  })
  readonly cubeSpaceRatio!: number;

  @InjectReactive()
  transformRotate: string;

  // data
  cubeList: CubeProps[] = [];
  showCenterCube = true;
  showCornerCube = true;
  showEdgeCube = true;
  startTurnInfo: TurnInfo = null;
  // endTurnInfo: TurnInfo = null;

  @Ref("box")
  boxDom: HTMLDivElement;

  // prvide
  @ProvideReactive()
  size: Size = this.cubeSize;

  @ProvideReactive()
  spaceRatio: number = this.cubeSpaceRatio;

  // computed
  get boxCls(): string {
    return prefix;
  }
  get boxStyle(): {
    transform: string;
  } {
    const { transformRotate } = this;
    return {
      transform: transformRotate,
    };
  }
  // 坐标最大值
  get coordMaxNum(): number {
    return getCoordMaxNum(this.order);
  }
  // 中心块
  get centerCubeList(): CubeProps[] {
    return this.cubeList.filter((item) => item.role === "center");
  }
  // 角块
  get cornerCubeList(): CubeProps[] {
    return this.cubeList.filter((item) => item.role === "corner");
  }
  // 棱块
  get edgeCubeList(): CubeProps[] {
    return this.cubeList.filter((item) => item.role === "edge");
  }
  get cubeList_res(): CubeProps[] {
    const {
      showCenterCube,
      showCornerCube,
      showEdgeCube,
      centerCubeList,
      cornerCubeList,
      edgeCubeList,
    } = this;
    const list = [] as CubeProps[];
    if (showCenterCube) {
      list.push(...centerCubeList);
    }
    if (showCornerCube) {
      list.push(...cornerCubeList);
    }
    if (showEdgeCube) {
      list.push(...edgeCubeList);
    }
    return list;
  }
  get cubeListGroup_x(): CubePropsGroupMap {
    return _groupBy(this.cubeList, ({ currentCoord }) => {
      const [x] = currentCoord;
      return x;
    });
  }
  get cubeListGroup_y(): CubePropsGroupMap {
    return _groupBy(this.cubeList, ({ currentCoord }) => {
      const [, y] = currentCoord;
      return y;
    });
  }
  get cubeListGroup_z(): CubePropsGroupMap {
    return _groupBy(this.cubeList, ({ currentCoord }) => {
      const [, , z] = currentCoord;
      return z;
    });
  }

  // methods
  // 开始旋转手势
  onStartTurn(config: CubeProps, position: FacePositionBase): void {
    this.startTurnInfo = {
      position,
      config,
    };
  }
  // 结束旋转手势
  onEndTurn(config: CubeProps, position: FacePositionBase): void {
    const { startTurnInfo } = this;
    if (startTurnInfo) {
      const endTurnInfo = {
        position,
        config,
      };
      // this.endTurnInfo = endTurnInfo
      this.toTurn(startTurnInfo, endTurnInfo);
    } else {
      console.warn(`开始手势信息未记录`);
    }
  }
  // 检测旋转手势列表
  checkTurnInfoList(startInfo: TurnInfo, endInfo: TurnInfo): boolean {
    let res = false;
    console.log(`
      startInfo
        startInfo.currentPosition ${startInfo.position}
        startInfo.currentCoord ${startInfo.config.currentCoord}
      endInfo
        endInfo.currentPosition ${endInfo.position}
        endInfo.currentCoord ${endInfo.config.currentCoord}
    `);
    if (startInfo.config === endInfo.config) {
      if (startInfo.position === endInfo.position) {
        console.warn(`开始结束为同一个块且为同一个面，无法判断旋转意图`);
      } else {
        console.warn(`暂不支持操纵同一个块的不同面
            后面计划支持检测同一块【非相对面】的旋转意图判断
          `);
      }
    } else {
      if (startInfo.position !== endInfo.position) {
        console.warn(
          `开始块的面与结束块的面不是一个方向的面，无法判断旋转意图`
        );
      } else {
        res = true;
      }
    }
    return res;
  }
  // 获取最终旋转的轴线和最终旋转的一排块
  getAxisAndTargetList(
    { position: startPosition, config: { currentCoord: startCoord } }: TurnInfo,
    { config: { currentCoord: endCoord } }: TurnInfo
  ): {
    axis: Axis;
    targetRowCubeList: CubeProps[];
  } {
    const { cubeListGroup_x, cubeListGroup_y, cubeListGroup_z } = this;
    const cubeListGroup_list = [
      cubeListGroup_x,
      cubeListGroup_y,
      cubeListGroup_z,
    ];
    // 包含开始结束块的排
    const containStartEndRowList = cubeListGroup_list
      .map((item, axis) => {
        // 当前坐标轴维度下的 【排值】【即魔方旋转时一排一排旋转的】
        const groupList: CubePropsGroup = Object.values(item);
        // 目标排块列表【即选择是一排一排旋转的】
        const targetRowCubeList = groupList.find((row) => {
          // 当前排 的块的坐标组成的数组
          const coordList = row.map((item) => item.currentCoord);
          return coordList.includes(startCoord) && coordList.includes(endCoord);
        });
        if (targetRowCubeList) {
          return {
            targetRowCubeList,
            axis,
          };
        }
      })
      .filter((item) => item);

    // 通过开始结束的两个块坐标筛选可能最终旋转的坐标轴
    // const axisListAfterFilterByCoord: Axis[] = containStartEndRowList.map(
    // (item) => item.axis
    // );

    // console.log(
    //   246,
    //   [...startCoord],
    //   [...endCoord],
    //   containStartEndRowList.map(({ axis }) => Axis[axis])
    // );
    // 块的面相当的坐标轴
    const positionQuiteAxis = positionQuiteAxisObject[startPosition];
    // 通过块的触发面筛选可能最终旋转的坐标轴
    const axisListAfterFilterByposition: Axis[] = [
      Axis.x,
      Axis.y,
      Axis.z,
    ].filter((item) => item !== positionQuiteAxis);

    // console.log(
    //   297,
    //   axisListAfterFilterByCoord.map((item) => Axis[item])
    // );
    // console.log(
    //   298,
    //   axisListAfterFilterByposition.map((item) => Axis[item])
    // );
    return containStartEndRowList.find(({ axis }) =>
      axisListAfterFilterByposition.includes(axis)
    );
  }
  // 获取旋转方向
  getRotatedirection(
    axis: Axis,
    { position: startPosition, config: { currentCoord: startCoord } }: TurnInfo,
    { config: { currentCoord: endCoord } }: TurnInfo
  ): Direction {
    const diffValIndex: Axis = startCoord.findIndex(
      (v, index) => v !== endCoord[index]
    );
    const endIsBig = startCoord[diffValIndex] - endCoord[diffValIndex] < 0;

    console.log(`
      axis ${Axis[axis]}
      diffValIndex ${Axis[diffValIndex]}
      start ${startCoord[diffValIndex]}
      end ${endCoord[diffValIndex]}
      endIsBig ${endIsBig}
      -------------------
      positiveFace[axis] ${positiveFace[axis]}
      positiveFace[axis].includes(startPosition) ${positiveFace[axis].includes(
        startPosition
      )}
      -------------------
      startPosition ${startPosition}
      startPosition ${FacePositionBase[startPosition]}
    `);
    let res: Direction;
    if (positiveFace[axis].includes(startPosition)) {
      res = endIsBig ? "+" : "-";
    } else {
      res = endIsBig ? "-" : "+";
    }
    // if (positiveFace2[[axis, diffValIndex].toString()] === startPosition) {
    //   res = endIsBig ? "+" : "-";
    // } else {
    //   res = endIsBig ? "-" : "+";
    // }
    console.log(338, res);
    return res;
  }
  // 执行
  getUpdateCubeCurrentInfo(
    axis: Axis,
    list: CubeProps[],
    direction: Direction,
    { config: { currentCoord: startCoord } }: TurnInfo
  ): {
    rotateChangeMap: RotateChangeMap;
    circleGroupList: CubeProps[];
  }[] {
    console.log(`旋转之后 更新旋转块的 currentCoord`);
    // 在以各坐标最大值的点为开始点 同时根据顺时针逆时针判断首先是哪个坐标变化即-1
    const axisChangeOrder: Axis[] = getAxisChangeOrderInfo(axis, direction);

    // 通过魔方圈数分组
    const currentCoordGroupByCircleMaxNum = _groupBy(list, (cubeProps) => {
      const { currentCoord } = cubeProps;
      const currentCoordAbs = currentCoord
        .filter((item, index) => axis !== index)
        .map((item) => Math.abs(item));
      // console.log(374, currentCoord, Math.max(...currentCoordAbs));
      return Math.max(...currentCoordAbs);
    });
    // console.log(346, _cloneDeep(currentCoordGroupByCircleMaxNum));
    return Object.keys(currentCoordGroupByCircleMaxNum).map((maxNumString) => {
      // 最高点坐标
      const topCoord: CubeCoord = [...startCoord];
      const maxNum = parseFloat(maxNumString);
      axisChangeOrder.forEach((axis) => {
        topCoord[axis] = maxNum;
      });
      const circleGroupList = currentCoordGroupByCircleMaxNum[maxNum];

      // console.log(
      //   356,
      //   maxNum,
      //   _cloneDeep(topCoord),
      //   _cloneDeep(circleGroupList)
      // );

      // 根据topCoord推导当前圈的坐标
      const list: CubeCoord[] = getDeductionCoordList(
        topCoord,
        axisChangeOrder,
        maxNum
      );

      // console.log(370, _cloneDeep(list));

      // 旋转的时候 每次移动的步数
      const step = list.length === 1 ? 0 : Math.max(list.length / 4);

      const listLength = list.length;
      // console.log(369, listLength);
      const rotateChangeMap = list.reduce((res, item, index) => {
        const targetIndex = index + step;
        // 即如果listLength === 1 nextIndex即为0 即当前块本身
        const nextIndex =
          listLength === 1
            ? 0
            : targetIndex >= listLength
            ? targetIndex - listLength
            : targetIndex;
        const nextCoord = list[nextIndex];
        // console.log(`
        //   374
        //   nextCoord ${nextCoord}
        //   circleGroupList.mpa(item => item.currentCoord) ${circleGroupList.map(
        //     (item) => item.currentCoord
        //   )}
        // `);
        const targetCubeProps = circleGroupList.find(
          (item) => item.currentCoord.toString() === nextCoord.toString()
        );
        res[item.toString()] = _cloneDeep(targetCubeProps);
        return res;
      }, {} as RotateChangeMap);

      return {
        rotateChangeMap,
        circleGroupList,
      };
    });
  }
  // 执行旋转
  toTurn(startInfo: TurnInfo, endInfo: TurnInfo): void {
    if (this.checkTurnInfoList(startInfo, endInfo)) {
      // console.log(271, { ...startInfo }, { ...endInfo });
      const { axis, targetRowCubeList } =
        this.getAxisAndTargetList(startInfo, endInfo) || {};
      if (axis === undefined) {
        return console.warn("没找到axis 问题待处理");
      }
      const direction = this.getRotatedirection(axis, startInfo, endInfo);
      console.log(
        `绕${Axis[axis]}轴${direction === "+" ? "顺" : "逆"}时针旋转`
      );

      Object.values(
        this.getUpdateCubeCurrentInfo(
          axis,
          targetRowCubeList,
          direction,
          startInfo
        )
      ).forEach(({ circleGroupList, rotateChangeMap }) => {
        circleGroupList.forEach((item) => {
          const { rotateRatioConfig, currentCoord, translateRatioConfig } =
            item;
          const targetCubeProps = rotateChangeMap[currentCoord.toString()];
          const {
            currentCoord: targetCoord,
            translateRatioConfig: targetTranslateRatioConfig,
          } = targetCubeProps;

          rotateRatioConfig[axis] += parseFloat(`${direction}1`);

          setTimeout(() => {
            item.boxTransition = false;

            currentCoord.splice(0, 3, ...targetCoord);
            rotateRatioConfig[axis] = 0;
            Object.assign(translateRatioConfig, targetTranslateRatioConfig);
          }, 300);
        });
      });
    }
    this.startTurnInfo = null;
  }

  afterUpdated(config: CubeProps): void {
    config.boxTransition = true;
  }

  // 生命周期
  created(): void {
    this.cubeList = getCubeList(this.order);
  }
  mounted(): void {
    this.boxDom.oncontextmenu = () => false;
  }
  beforeDestroy(): void {
    this.boxDom.oncontextmenu = null;
  }

  // watch

  @Watch("order")
  onOrderChange(): void {
    this.cubeList = getCubeList(this.order);
  }

  @Watch("cubeSize")
  onCubeSizeChange(): void {
    this.size = this.cubeSize;
  }

  @Watch("cubeSpaceRatio")
  onCubeSpaceRatioChange(): void {
    this.spaceRatio = this.cubeSpaceRatio;
  }
}
</script>
<style lang="less" scoped>
@prefix: rubik-cube;
.@{prefix}{
  position: relative;
  transform-style: preserve-3d;
  transition: transform 300ms linear;
  /deep/
  &, * {
    user-select: none;
  }
}
</style>
